package com.lightsh.motioner;

import com.lightsh.motioner.MultiPointTracker.OnGestureTrackListener;

import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 根据用户Motion，修改屏幕的位置
 *
 */
public class ScreenTracker {
	
	
	private static final String TAG = "ScreenTracker";
	private static final boolean DEBUG = false;
	
	protected Rect mDstRect;
	
	protected int mScreenW;
	protected int mScreenH;
	
	protected int mTotalScreenW;
	protected int mTotalScreenH;
	
	/**
	 * 用户滑动屏幕时，屏幕实际移动的比例
	 */
	private float mMoveProportion = 1.2f;
    
    
    /**
     * 最大缩放比例
     */
    private float mMaxScale = 4.0f;
    
    /**
     * 最小缩放比例
     */
    private float mMinScale = 0.5f;
    /**
     * 当前的屏幕缩放比例
     */
    protected float mScale = 1f;
    
    private MultiPointTracker mPointTracker;
	
    // FIXME 使用RectF而下面所有的int强制转换都去掉??
    // FIXME 使用Matrix来提高效率？
	public ScreenTracker(int screenW, int screenH, int totalScreenW, int totalScreenH){
		mDstRect = new Rect(0, 0, (int)(screenW / mScale), (int)(screenH / mScale));
		mScreenW = screenW;
		mScreenH = screenH;
		mTotalScreenW = totalScreenW;
		mTotalScreenH = totalScreenH;
		
		mPointTracker = new MultiPointTracker(mTrackListener);
	}
	
	private OnGestureTrackListener mTrackListener = new OnGestureTrackListener() {
		
		@Override
		public boolean onZoom(float lastDistance, float currentDistance,
				PointF centerPoint) {
			float distance = currentDistance - lastDistance;
			float needScale = currentDistance/lastDistance;
			if(Math.abs(distance) > 5f){
				float currentScale = mScale * currentDistance/lastDistance;
//				logD("distance = " + distance);
				logD("scale proportion= " + needScale + ", currentScale = " + currentScale);
				changeScaleByPoint(currentScale, centerPoint.x, centerPoint.y);
//				setScreenScale(currentScale);
			}
			
			return true;
		}
		
		@Override
		public boolean onDrag(float lastX, float lastY, float currentX,
				float currentY) {
			final int deltaX = (int) (lastX - currentX);
			final int deltaY = (int) (lastY - currentY);
			if(deltaX != 0 || deltaY != 0){
				moveScreenForward((int)(deltaX * mMoveProportion / mScale), (int)(deltaY * mMoveProportion / mScale));
			}			
			return true;
		}
	};
	
	/**
	 * 设置用户滑动屏幕时，屏幕实际移动的比例值
	 */
	public void setMoveProportion(float proportion){
		mMoveProportion = proportion;
	}
	
	/**
	 * 调用该方法来相应用户动作
	 */
	public boolean onTouchEvent(MotionEvent ev){
		return mPointTracker.onTouchEvent(ev);
	}
    
    /**
     * 获取缩放后的屏幕宽度
     */
    private float getScaleScreenW(float scale){
    	return mScreenW / scale;
    }
    /**
     * 获取缩放后的屏幕高度
     */
    private float getScaleScreenH(float scale){
    	return mScreenH / scale;
    }
	
    /**
     * 根据输入的比例，获取与目标值最接近的合法的x位置
     * @param dstX			目标x
     * @param scale			缩放比例
     * @return				最终的x位置
     */
	private int getFixXPosition(int dstX, float scale){
		return (int) (Math.max(0, Math.min(dstX, mTotalScreenW - getScaleScreenW(mScale))));
	}
	/**
     * 根据输入的比例，获取与目标值最接近的合法的y位置
     * @param dstX			目标y
     * @param scale			缩放比例
     * @return				最终的y位置
     */
	private int getFixYPosition(int dstY, float scale){
		return (int) (Math.max(0, Math.min(dstY, mTotalScreenH - getScaleScreenH(mScale))));
	}
	/**
	 * 移动屏幕(调整显示世界中的哪一个范围的物体)
	 * @param stepX 屏幕向右或左(根据正负)移动的距离，以px为单位
	 * @param stepY 屏幕向下或上(根据正负)移动的距离，以px为单位
	 */
	public synchronized void moveScreenForward(int stepX, int stepY){
		if(stepX != 0){
			int dstX = mDstRect.left + stepX;
			moveScreenXTo(getFixXPosition(dstX, mScale));
		}
		if(stepY != 0){
			int dstY = mDstRect.top + stepY;
			moveScreenYTo(getFixYPosition(dstY, mScale));
		}
	}
	
	/**
	 * 将屏幕移动到世界的指定位置的,并且会根据缩放比例对x、y位置做合法判断，得到不会越级的值
	 * @param xPosition		屏幕左边所处的坐标
	 * @param yPosition		屏幕上边所处的坐标
	 */
	public synchronized void checkAndMoveScreenTo(int xPosition, int yPosition){
		moveScreenXTo(getFixXPosition(xPosition, mScale));
		moveScreenYTo(getFixYPosition(yPosition, mScale));
	}
	
	/**
	 * 将屏幕移动到世界的指定位置的,不会做有效性检查
	 * @param xPosition		屏幕左边所处的坐标
	 * @param yPosition		屏幕上边所处的坐标
	 */
	public synchronized void moveScreenTo(int xPosition, int yPosition){
		moveScreenXTo(xPosition);
		moveScreenYTo(yPosition);
	}
	
	/**
	 * 将rect移动的指定的x位置，不会做合法判断。
	 */
	private void moveScreenXTo(int xPosition){
		mDstRect.left = xPosition;
		mDstRect.right = Math.round(xPosition + getScaleScreenW(mScale));
		logD("x -> " + mDstRect.left);
	}
	/**
	 * 将rect移动的指定的y位置，不会做合法判断。
	 */
	private void moveScreenYTo(int yPosition){
		mDstRect.top = yPosition;
		mDstRect.bottom = Math.round(yPosition + getScaleScreenH(mScale));
		logD("y -> " + mDstRect.top);
	}
	
	public int getTotalScreenWidth(){
		return mTotalScreenW;
	}
	
	public int getTotalScreenHeight(){
		return mTotalScreenH;
	}
	
	public synchronized Rect getDstRectClone(){
		return new Rect(mDstRect);
	}
	
	/**
	 * 获取当前的缩放比例
	 */
	public synchronized float getScreenScale(){
		return mScale;
	}
	/**
	 * 设置当前缩放比例,根据当前rect的左上角位置来放大(缩小)
	 */
	public synchronized void setScreenScale(float scale){
		mScale = getFixScall(scale, mDstRect.left, mDstRect.top);		
		// 我们需要重新计算rect的位置
		checkAndMoveScreenTo(mDstRect.left, mDstRect.top);
		
	}
	
	/**
	 * 根据给定的固定值x/y，计算出与目标比例最接近的合法比例
	 * @param dstScale			目标比例
	 * @param constX			x位置
	 * @param constY			y位置
	 * @return					最终的比例
	 */
	private float getFixScall(float dstScale, final int constX, final int constY){
		// 计算出逻辑上允许的最小值，该值的临界点为世界的右边刚好与屏幕右边重合:  x位置 + 屏幕的比例宽度 == 世界的右边
		// (逻辑上没有最大值，因为实际上可以允许无限的放大)
		
		final float minScallX = (float)mScreenW / (mTotalScreenW - constX);
		final float minScallY = (float)mScreenH / (mTotalScreenH - constY);
		
		// 计算出与逻辑上允许的与dstScale最接近的值
		float fixScale = Math.max(dstScale, Math.max(minScallX, minScallY));
		
		// 最后要根据设置的上下限值做约束
		return Math.min(mMaxScale, Math.max(mMinScale, fixScale));
	}
	
	/**
	 * 需要根据一个固定点和目标比例，计算出一个合法的比例
	 * <br/>当其中有边处于world的边沿时，需要做特殊处理（将边沿作为固定点）
	 * @param dstScale			目标比例
	 * @param constX			中心点x，它代表的是屏幕坐标上的点，而非world坐标系上的点
	 * @param constY			中心点y，它代表的是屏幕坐标上的点，而非world坐标系上的点
	 * @return					
	 */
	private void changeScaleByPoint(float dstScale, float constX, float constY){
		if(dstScale == mScale){
			// equal,noting need do
			return;
		}
		
		dstScale = Math.min(mMaxScale, Math.max(mMinScale, dstScale));
		
		// 最终需要变成的比例
		float fixScale;
		
		// 根据屏幕坐标的中心点，找出对应点的world坐标
		float currentWorldCenterX = mDstRect.left + constX / mScale;
		float currentWorldCenterY = mDstRect.top + constY / mScale;
		
		if(dstScale > mScale){
			// 放大,这里只需要对最大最小zoom判断
			fixScale = dstScale;
		}else{
			// 缩小
			
			// FIXME 如果有边贴近边沿，则改变中心点为边沿
			if((mDstRect.left == 0 || mDstRect.right == mTotalScreenW) && constX != mScreenW && constX != 0){
//			if( (mDstRect.left == 0 && constX != 0) || (mDstRect.right == mTotalScreenW && constX != mScreenW)){
				changeScaleByPoint(dstScale, mDstRect.left == 0 ? 0 : mScreenW, constY);
				return;
			}
			if((mDstRect.top == 0 || mDstRect.bottom == mTotalScreenH) && constY != mScreenH && constY != 0){
//			if((mDstRect.top == 0 && constY != 0) || (mDstRect.bottom == mTotalScreenH && constY != mScreenH)){
				changeScaleByPoint(dstScale, constX, mDstRect.top == 0 ? 0 : mScreenH);
				return;
			}
			
			float minScallX = 0;
			float minScallY = 0;
			// 找出合法的最小比例
			
			// (世界宽度 - 世界中点x)*最小比例 = 屏幕宽度 - 屏幕中点x 
			if(mTotalScreenW != currentWorldCenterX){
				minScallX = (mScreenW - constX) / (mTotalScreenW - currentWorldCenterX);
			}
			if(currentWorldCenterX != 0){
				minScallX = Math.max(minScallX, constX / currentWorldCenterX);
			}
			
			if(mTotalScreenH != currentWorldCenterY){
				minScallY = (mScreenH - constY) / (mTotalScreenH - currentWorldCenterY);
			}
			if(currentWorldCenterY != 0){
				minScallY = Math.max(minScallY, constY / currentWorldCenterY);
			}
			fixScale = Math.max(dstScale, Math.max(minScallX, minScallY));			
		}
		// newLeftTopX + constX / newScale = oldLeftTopX + constX / oldScale(新的世界坐标与旧的世界坐标在 constX 屏幕上这一点的坐标值一样)
		
		float dstWorldLeftTopX = currentWorldCenterX - constX / fixScale;
		float dstWorldLeftTopY = currentWorldCenterY - constY / fixScale;
		if((int)dstWorldLeftTopX < 0 || (int)dstWorldLeftTopY < 0 || fixScale < mMinScale || fixScale > mMaxScale){
			logE("wrong : " + mScale + "," + dstScale + "," + constX + "," + constY + "," + dstWorldLeftTopX + "," 
					+ dstWorldLeftTopY + "," + fixScale + "," + mDstRect.left + "," + mDstRect.top);
			// FIXME
			throw new RuntimeException( TAG + " : " + "(int)dstWorldLeftTopX < 0 || (int)dstWorldLeftTopY < 0 || fixScale < mMinScale || fixScale > mMaxScale");
		}
		
		logD("mScale = " + mScale + " -> " + fixScale);
		mScale = fixScale;
		moveScreenTo(Math.round(dstWorldLeftTopX), Math.round(dstWorldLeftTopY));
	}
	
	private void logD(String tip){
		if(DEBUG){
    		Log.d(TAG, "" + tip);
    	}
	}
	private void logE(String tip){
    	Log.e(TAG, "" + tip);
	}
}
